package com.clementheliou.cheliou.web.core.servlet;

import static org.apache.commons.io.IOUtils.close;
import static org.apache.commons.io.IOUtils.closeQuietly;
import static org.apache.commons.io.IOUtils.copy;
import static org.apache.commons.lang3.StringUtils.contains;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * {@link Servlet} used to load images from a given repository outside of the application. By this
 * way, we are able to manage images independently from the webapp lifecycle. Moreover, the
 * application size won't grow up due to the images number increase.
 * <p>
 * The repository to read in is defined in the application descriptor ( <tt>web.xml</tt>) and should
 * be different from an environment to another. Finally, this code has been inspired by the
 * following thread:
 * <ul>
 * <li>https://forums.oracle.com/forums/thread.jspa?threadID=1718143</li>
 * </ul>
 * </p>
 * 
 * @author Clément HELIOU (clement.heliou@gmail.com)
 * @see {@link HttpServlet}.
 * @since 1.0
 */
public class ImageLoaderServlet extends HttpServlet {

	/**
	 * Logger for this class.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @see {@link Logger};
	 * @see {@link LoggerFactory}.
	 * @since 1.0
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(ImageLoaderServlet.class);

	/**
	 * UID for serialization.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private static final long serialVersionUID = 1009582503979001756L;

	/**
	 * Name of the parameter to be loaded during the initialization to get the allowed content
	 * types.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	public static final String INIT_PARAM_ALLOWED_CONTENT_TYPE = "allowedContentTypes";

	/**
	 * Name of the parameter to be loaded during the initialization to get the default file if the
	 * requested one is not found.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	public static final String INIT_PARAM_DEFAULT_FILE_ON_404 = "defaultFileOn404";

	/**
	 * Name of the parameter to be loaded during the initialization to get the remote URI.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	public static final String INIT_PARAM_REMOTE_URI = "remoteURI";

	/**
	 * Cache for loaded images avoiding to get the same content many times in the application
	 * lifecycle. The images content does not change inside it.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private Map<String, byte[]> imagesCache = new HashMap<String, byte[]>();

	/**
	 * The images content types that this servlet is able to load.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private String allowedContentTypes;

	/**
	 * Path to the image to be displayed if the requested one is not found.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private String defaultFilePathOn404;

	/**
	 * The remote URI to access this {@link Servlet}.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private String remoteURI;

	/**
	 * <i>{@inheritDoc} </i>
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpURLConnection connection = null;
		InputStream inputStream = null;
		OutputStream outputStream = null;

		try {
			final String imagePath = request.getPathInfo();
			byte[] imageContent = imagesCache.get(imagePath);

			if (imageContent == null) {
				LOGGER.debug("The image {} can't be found in the cache. Will now be loaded.", imagePath);

				final URL remoteUrl = new URL(remoteURI.concat(imagePath));
				connection = (HttpURLConnection) remoteUrl.openConnection();
				final int statusCode = connection.getResponseCode();

				if (statusCode != HttpURLConnection.HTTP_OK || !contains(allowedContentTypes, connection.getContentType())) {
					LOGGER.debug("{} not found, send redirect to default file.", remoteURI.concat(imagePath));
					response.sendRedirect(defaultFilePathOn404);
					return;
				}

				imageContent = IOUtils.toByteArray(connection.getInputStream());
				imagesCache.put(imagePath, imageContent);
			}

			LOGGER.debug("Get the image {} from the cache.", imagePath);

			inputStream = new ByteArrayInputStream(imageContent);
			outputStream = response.getOutputStream();
			copy(inputStream, outputStream);
		} catch (MalformedURLException mue) {
			LOGGER.error("Exception during the recovery of {}.", remoteURI.concat(request.getPathInfo()));
			throw new ServletException(mue);
		} finally {
			close((URLConnection) connection);
			closeQuietly(inputStream);
			closeQuietly(outputStream);
		}
	}

	/**
	 * <i>{@inheritDoc}</i>
	 * <p>
	 * Same behavior than {@link #doGet(HttpServletRequest, HttpServletResponse)}.
	 * </p>
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

	/**
	 * <i>{@inheritDoc}</i>
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Override
	public void init(ServletConfig config) throws ServletException {
		LOGGER.debug("Initialization of the servlet.");

		allowedContentTypes = config.getInitParameter(INIT_PARAM_ALLOWED_CONTENT_TYPE);
		defaultFilePathOn404 = config.getInitParameter(INIT_PARAM_DEFAULT_FILE_ON_404);
		remoteURI = config.getInitParameter(INIT_PARAM_REMOTE_URI);

		LOGGER.debug("Servlet initialized with '{}' as remote URI, '{}' as default file and '{}' as content types.", remoteURI, defaultFilePathOn404, allowedContentTypes);
	}

	/*
	 * Getters & setters, sometimes for test purpose.
	 */

	public String getAllowedContentTypes() {
		return allowedContentTypes;
	}

	public String getRemoteURI() {
		return remoteURI;
	}

	public String getDefaultFileOn404() {
		return defaultFilePathOn404;
	}

	public void setAllowedContentTypes(String allowedContentTypes) {
		this.allowedContentTypes = allowedContentTypes;
	}

	public void setDefaultFileOn404(String defaultFileOn404) {
		this.defaultFilePathOn404 = defaultFileOn404;
	}

	public void setRemoteURI(String remoteURI) {
		this.remoteURI = remoteURI;
	}

}
